﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluidSystem;
using Tao.OpenGl;
using Tao.Platform;
using VectorMath;

namespace Candle
{
    public class Simulation
    {
        public int FramesPerSecond;

        FluidSystem.FluidSystem SimFluidSystem;

        bool simulate;
        public int GridWidth, GridHeight, GridDepth;

        float dt;
        private long nextT;
        private int f;

        public List<List<Pnt3F>> wicks = new List<List<Pnt3F>>();

        public Simulation()
        {
            CreateFluidSystem();

            Console.Out.WriteLine(
@"======================
Hello!
Click and drag to pan the view around.
Use the mouse wheel to zoom in and out.
Hit space to start/stop.
Hit 'r' to reset the simulation.
Hit 'c' to reset the camera.
======================");
        }

        private void CreateFluidSystem()
        {
            FramesPerSecond = 100; // 1/fps = dt
            dt = 1.0f / FramesPerSecond;
            simulate = false;
            nextT = System.DateTime.Now.Ticks + (long)(10000000 * dt);


            GridWidth = 6;
            GridHeight = 6;
            GridDepth = 6;

            SimFluidSystem = new FluidSystem.FluidSystem(GridWidth, GridHeight, GridDepth);
            f = 0;

            Random r = new Random(7);
            List<Particle> ps = new List<Particle>();

            //make some particles
            //          for (int i = GridWidth / 4; i <= GridWidth * 3 / 4; i++)
            for (int j = 0; j <= GridHeight * 3 / 4; j++)
            //                    for (int k = GridDepth / 4; k <= GridDepth * 3 / 4; k++)
            {
                for (int l = 0; l < 192; l++)
                {
                    double radius = Math.Max(r.NextDouble(), r.NextDouble()) * Math.Sqrt(GridDepth * GridWidth) / 4.0;
                    double angle = r.NextDouble() * Math.PI * 2;
                    ps.Add(new Particle(new Pnt3D(
                        GridWidth / 2.0 + Math.Cos(angle) * radius,
                        j + r.NextDouble() * 0.98 + 0.01,
                        GridDepth / 2.0 + Math.Sin(angle) * radius)));
                }
            }
            SimFluidSystem.Particles = ps;

            ////warm an area
            //for (int i = 2; i <= 5; i++)
            //    for (int j = 4; j <= 6; j++)
            //        for (int k = 2; k <= 5; k++)
            //        {
            //            SimFluidSystem.setCellTemp(i, j, k, 2 * SimFluidSystem.liquidTransition);
            //        }

            //add some wicks to burn
            wicks.Add(new List<Pnt3F>());
            for (int j = 0; j < GridHeight - 1; j++)
            {
                wicks[0].Add(new Pnt3F(GridWidth / 2 + 0.1f + (float)r.NextDouble() * -0.2f, j + 0.5f, GridDepth / 2 + 0.1f + (float)r.NextDouble() * -0.2f));
            }
            //wicks.Add(new List<Pnt3F>());
            //for (int j = 0; j < GridHeight - 1; j++)
            //{
            //    wicks[1].Add(new Pnt3F(GridWidth / 4 + 0.5f + (float)r.NextDouble() * 0.2f, j + 0.5f, GridDepth / 4 + 0.5f + (float)r.NextDouble() * 0.2f));
            //}
        }


        System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch();
        System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();

        internal void step()
        {
            //cap frame rate //ten million ticks per second
            if (simulate && System.DateTime.Now.Ticks >= nextT)
            {
                //update system since dt has actually gone by

                BurnWicks();

                sw1.Reset();
                sw1.Start(); //time the physics step

                SimFluidSystem.timeStep(dt);
                nextT = System.DateTime.Now.Ticks + (long)(10000000 * dt); //set next frame time

                sw1.Stop();

                sw2.Stop();
                Console.Out.WriteLine("Frame: " + f++ + " took " + "{0:G4}" + " seconds. " +
                                        "The physics took " + "{1:G4}" + " seconds.",
                                        sw2.ElapsedMilliseconds / 1000.0f, sw1.ElapsedMilliseconds / 1000.0f);
                sw2.Reset();
                sw2.Start(); //time everything else
            }
        }

        private void BurnWicks()
        {
            foreach (List<Pnt3F> wick in wicks)
            {
                for (int i = 1; i < wick.Count; i++)
                {
                    if (f > 1 && SimFluidSystem.CellsStates[(int)(wick[i].x), (int)(wick[i].y), (int)(wick[i].z)] != FluidSystem.FluidSystem.State.FULL
                        && SimFluidSystem.CellsStates[(int)(wick[i].x), (int)(wick[i].y + 1), (int)(wick[i].z)] == FluidSystem.FluidSystem.State.EMPTY)
                    {
                        SimFluidSystem.setCellTemp(wick[i].x, wick[i].y, wick[i].z, SimFluidSystem.liquidTransition * 12);
                        for (int face = 0; face < 6; face++)
                        {
                            int iOff = (face == 0) ? -1 : ((face == 1) ? 1 : 0);
                            int jOff = (face == 2) ? -1 : ((face == 3) ? 1 : 0);
                            int kOff = (face == 4) ? -1 : ((face == 5) ? 1 : 0);
                            SimFluidSystem.setCellTemp(wick[i].x + iOff, wick[i].y + jOff, wick[i].z + kOff, SimFluidSystem.liquidTransition * 2);
                        }
                    }
                }
            }
        }

        private void DrawWicks()
        {
            //draw wicks
            foreach (List<Pnt3F> wick in wicks)
            {
                for (int i = 1; i < wick.Count; i++)
                {
                    if (f > 1 && SimFluidSystem.CellsStates[(int)(wick[i].x), (int)(wick[i].y), (int)(wick[i].z)] != FluidSystem.FluidSystem.State.FULL
                         && SimFluidSystem.CellsStates[(int)(wick[i].x), (int)(wick[i].y + 1), (int)(wick[i].z)] == FluidSystem.FluidSystem.State.EMPTY)
                    {
                        Gl.glPushMatrix();
                        Gl.glTranslatef(wick[i].x + 0.0f, wick[i].y + 0.0f, wick[i].z + 0.0f);

                        Gl.glColor4f(0.75f, 0.25f, 0.00f, 0.75f);

                        Glu.GLUquadric q2 = Glu.gluNewQuadric();
                        Glu.gluQuadricNormals(q2, Glu.GLU_SMOOTH);
                        Glu.gluQuadricTexture(q2, Gl.GL_TRUE);
                        Glu.gluSphere(q2, 0.1, 3, 3);
                        Gl.glPopMatrix();
                    }
                }
            }
        }

        internal void display()
        {
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
            Gl.glClearColor(0, 0, 0, 0);

            DrawWorldBoundaries();

            //if (drawWireframe) Gl.glPolygonMode(Gl.GL_FRONT, Gl.GL_LINE);
            //else 
            Gl.glPolygonMode(Gl.GL_FRONT, Gl.GL_FILL);

            //draw system (since either time changed or the window did)
            foreach (Particle p in SimFluidSystem.Particles)
            {
                Gl.glPushMatrix();
                Gl.glTranslatef(p.X.xf, p.X.yf, p.X.zf);

                ////by state
                //if (SimFluidSystem.CellsStates[(int)(p.X.x ), (int)(p.X.y ), (int)(p.X.z )] == FluidSystem.FluidSystem.State.FULL)
                //    Gl.glColor4f(0.75f, 0.75f, 0, 0.1f);
                //else if (SimFluidSystem.CellsStates[(int)(p.X.x ), (int)(p.X.y ), (int)(p.X.z )] == FluidSystem.FluidSystem.State.SURFACE)
                //    Gl.glColor4f(0.0f, 0.75f, 0.75f, 0.1f);
                //else
                //    Gl.glColor4f(0.75f, 0.0f, 0.00f, 0.9f);

                //by temp
                double temp = SimFluidSystem.Temperatures[(int)(p.X.x), (int)(p.X.y), (int)(p.X.z)];
                temp = Math.Max(0, temp - SimFluidSystem.solidTransition);
                double liquidFactor = temp / (SimFluidSystem.liquidTransition - SimFluidSystem.solidTransition);
                liquidFactor = Math.Min(liquidFactor, 1.0f); //can't be more than 100% liquid
                Gl.glColor4d(liquidFactor / 3.0f + 0.7f, 0.7f, 0.4f, 0.6f - liquidFactor / 3.0f);

                Glu.GLUquadric q2 = Glu.gluNewQuadric();
                Glu.gluQuadricNormals(q2, Glu.GLU_SMOOTH);
                Glu.gluQuadricTexture(q2, Gl.GL_TRUE);
                Glu.gluSphere(q2, 0.2, 16, 16);
                Gl.glPopMatrix();
            }

            DrawWicks();

            //draw velocity field
            //Gl.glColor4d(0.0, 0.0, 1.0, 1.0);
            //Gl.glLineWidth(3.0f);
            //float dt_cfl = SimFluidSystem.dt_cfl;
            //for (double i = 0.5; i < GridWidth; i++)
            //    for (double j = 0.5; j < GridHeight; j++)
            //        for (double k = 0.5; k < GridDepth; k++)
            //        {
            //            Pnt3D v = SimFluidSystem.getVelocity(new Pnt3D(i, j, k), SimFluidSystem.Velocities);
            //            Gl.glBegin(Gl.GL_LINE_STRIP);
            //            Gl.glVertex3d(i, j, k);
            //            Gl.glVertex3d(i + v.x * dt_cfl, j + v.y * dt_cfl, k + v.z * dt_cfl);
            //            Gl.glEnd();
            //        }

            //if (frameExporter != null) frameExporter.writeFrame();
            if (f != lastF)
            {
                lastF = f;
                TaoForm.ScreenShot("candlePics/candle" + f + ".png", System.Drawing.Imaging.ImageFormat.Png);
            }
        }

        int lastF;

        private void DrawWorldBoundaries()
        {
            /// DRAW COMPUTATIONAL CELL BOUNDARY:

            if (simulate)
                Gl.glColor4d(0.0, 0.5, 0.0, 0.5);
            else
                Gl.glColor4d(1.0, 0.0, 0.0, 0.5);

            for (int k = 0; k < GridDepth + 1; k++)
            {
                //square orthog to Z
                Gl.glBegin(Gl.GL_LINE_STRIP);
                Gl.glVertex3d(0, 0, k + 0);
                Gl.glVertex3d(GridWidth + 0, 0, k + 0);
                Gl.glVertex3d(GridWidth + 0, GridHeight + 0, k + 0);
                Gl.glVertex3d(0 + 0, GridHeight + 0, k + 0);
                Gl.glVertex3d(0 + 0, 0 + 0, k + 0);
                Gl.glEnd();
            }
            for (int j = 0; j < GridHeight + 1; j++)
            {
                //square orthog to Y
                Gl.glBegin(Gl.GL_LINE_STRIP);
                Gl.glVertex3d(0, j + 0, 0);
                Gl.glVertex3d(GridWidth + 0, j + 0, 0 + 0);
                Gl.glVertex3d(GridWidth + 0, j + 0, GridDepth + 0);
                Gl.glVertex3d(0 + 0, j + 0, GridDepth + 0);
                Gl.glVertex3d(0 + 0, j + 0, 0 + 0);
                Gl.glEnd();
            }
            for (int i = 0; i < GridWidth + 1; i++)
            {
                //square orthog to X
                Gl.glBegin(Gl.GL_LINE_STRIP);
                Gl.glVertex3d(i + 0, 0, 0 + 0);
                Gl.glVertex3d(i + 0, 0, GridDepth + 0);
                Gl.glVertex3d(i + 0, GridHeight + 0, GridDepth + 0);
                Gl.glVertex3d(i + 0 + 0, GridHeight + 0, 0 + 0);
                Gl.glVertex3d(i + 0 + 0, 0 + 0, 0 + 0);
                Gl.glEnd();
            }


            //origin
            Gl.glPushMatrix();
            Gl.glColor3f(1.0f, 1.0f, 1.0f);
            Gl.glTranslatef(0, 0, 0);
            Glu.GLUquadric q2 = Glu.gluNewQuadric();
            Glu.gluQuadricNormals(q2, Glu.GLU_SMOOTH);
            Glu.gluQuadricTexture(q2, Gl.GL_TRUE);
            Glu.gluSphere(q2, 0.1, 4, 4);
            Gl.glPopMatrix();

            //axes
            Gl.glColor4d(1.0, 1.0, 1.0, 0.5);
            Gl.glBegin(Gl.GL_LINE_STRIP);
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(0, 0, GridDepth + 1);
            Gl.glEnd();
            Gl.glBegin(Gl.GL_LINE_STRIP);
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(0, GridHeight + 1, 0);
            Gl.glEnd();
            Gl.glBegin(Gl.GL_LINE_STRIP);
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(GridWidth + 1, 0, 0);
            Gl.glEnd();
        }

        internal void init()
        {
            //initialize stuff that is based off TaoForm existing first

            //Console.Out.WriteLine("INIT GL IS: something");

            Gl.glLineWidth(1.0f);
            Gl.glPointSize(1.0f);

            Gl.glDisable(Gl.GL_DEPTH_TEST);

            Gl.glEnable(Gl.GL_BLEND);
            Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
            //Gl.glBlendFunc(Gl.GL_SRC_ALPHA_SATURATE, Gl.GL_ONE_MINUS_SRC_ALPHA);
            //Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE);
            Gl.glEnable(Gl.GL_POINT_SMOOTH);
            Gl.glHint(Gl.GL_POINT_SMOOTH_HINT, Gl.GL_NICEST);
            Gl.glEnable(Gl.GL_LINE_SMOOTH);
            Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_NICEST);
            Gl.glEnable(Gl.GL_POLYGON_SMOOTH);
            Gl.glHint(Gl.GL_POLYGON_SMOOTH_HINT, Gl.GL_NICEST);
        }

        internal void dispatchKey(char p, System.Windows.Forms.KeyPressEventArgs e)
        {
            switch (p)
            {
                case ' ':
                    simulate = !simulate;
                    break;
                case 'c':
                    TaoForm.ViewMapping.resetCamera();
                    TaoForm.ViewMapping.apply_gluPerspective();
                    break;
                case 'r':
                    CreateFluidSystem();
                    break;
                default:
                    break;
            }
        }
    }
}
